// Copyright 2021 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef GRPC_TEST_CORE_TRANSPORT_BINDER_END2END_FUZZERS_FUZZER_UTILS_H
#define GRPC_TEST_CORE_TRANSPORT_BINDER_END2END_FUZZERS_FUZZER_UTILS_H

#include <memory>
#include <queue>
#include <string>
#include <thread>
#include <vector>

#include "absl/memory/memory.h"
#include "absl/status/status.h"

#include <grpc/support/log.h>

#include "src/core/ext/transport/binder/wire_format/binder.h"
#include "src/core/ext/transport/binder/wire_format/wire_reader.h"
#include "test/core/transport/binder/end2end/fuzzers/binder_transport_fuzzer.pb.h"

namespace grpc_binder {
namespace fuzzing {

// A WritableParcel implementation that simply does nothing. Don't use
// MockWritableParcel here since capturing calls is expensive.
class NoOpWritableParcel : public WritableParcel {
 public:
  int32_t GetDataSize() const override { return 0; }
  absl::Status WriteInt32(int32_t /*data*/) override {
    return absl::OkStatus();
  }
  absl::Status WriteInt64(int64_t /*data*/) override {
    return absl::OkStatus();
  }
  absl::Status WriteBinder(HasRawBinder* /*binder*/) override {
    return absl::OkStatus();
  }
  absl::Status WriteString(absl::string_view /*s*/) override {
    return absl::OkStatus();
  }
  absl::Status WriteByteArray(const int8_t* /*buffer*/,
                              int32_t /*length*/) override {
    return absl::OkStatus();
  }
};

// Binder implementation used in fuzzing.
//
// Most of its the functionalities are no-op, except ConstructTxReceiver now
// returns a TransactionReceiverForFuzzing.
class BinderForFuzzing : public Binder {
 public:
  BinderForFuzzing() : input_(std::make_unique<NoOpWritableParcel>()) {}

  explicit BinderForFuzzing(const binder_transport_fuzzer::IncomingParcels& p)
      : incoming_parcels_(p), input_(std::make_unique<NoOpWritableParcel>()) {}

  void Initialize() override {}
  absl::Status PrepareTransaction() override { return absl::OkStatus(); }

  absl::Status Transact(BinderTransportTxCode /*tx_code*/) override {
    return absl::OkStatus();
  }

  std::unique_ptr<TransactionReceiver> ConstructTxReceiver(
      grpc_core::RefCountedPtr<WireReader> wire_reader_ref,
      TransactionReceiver::OnTransactCb cb) const override;

  WritableParcel* GetWritableParcel() const override { return input_.get(); }
  void* GetRawBinder() override { return nullptr; }

 private:
  binder_transport_fuzzer::IncomingParcels incoming_parcels_;
  std::unique_ptr<WritableParcel> input_;
};

// ReadableParcel implementation used in fuzzing.
//
// It consumes a Parcel generated by mutator, and returns the data in the Parcel
// upon user's requests.
class ReadableParcelForFuzzing : public ReadableParcel {
 public:
  explicit ReadableParcelForFuzzing(const binder_transport_fuzzer::Parcel& p)
      : parcel_data_size_(p.data_size()), consumed_data_size_(0) {
    for (const auto& v : p.values()) {
      values_.push(v);
    }
  }

  // Construct from SetupTransportParcel, which have fixed types of data in it.
  explicit ReadableParcelForFuzzing(
      const binder_transport_fuzzer::SetupTransportParcel& p)
      : parcel_data_size_(p.data_size()), consumed_data_size_(0) {
    // Creates value for protocol version and put it into the queue
    binder_transport_fuzzer::Value version_value;
    version_value.set_i32(p.version());
    values_.push(version_value);

    // Creates a binder value and put it into the queue
    binder_transport_fuzzer::Value binder_value;
    binder_value.mutable_binder();  // sets one-of field
    values_.push(binder_value);
  }

  int32_t GetDataSize() const override;
  absl::Status ReadInt32(int32_t* data) override;
  absl::Status ReadInt64(int64_t* data) override;
  absl::Status ReadBinder(std::unique_ptr<Binder>* binder) override;
  absl::Status ReadByteArray(std::string* data) override;
  absl::Status ReadString(std::string* data) override;

 private:
  // Stores data/objects in binder in their order. Since we don't support random
  // access using a std::queue is enough here.
  std::queue<binder_transport_fuzzer::Value> values_;

  const int32_t parcel_data_size_;

  static constexpr size_t kParcelDataSizeLimit = 1024 * 1024;
  size_t consumed_data_size_;
};

void JoinFuzzingThread();

// TransactionReceiver implementation used in fuzzing.
//
// When constructed, start sending fuzzed requests to the client. When all the
// bytes are consumed, the reference to WireReader will be released.
class TransactionReceiverForFuzzing : public TransactionReceiver {
 public:
  TransactionReceiverForFuzzing(
      binder_transport_fuzzer::IncomingParcels incoming_parcels,
      grpc_core::RefCountedPtr<WireReader> wire_reader_ref,
      TransactionReceiver::OnTransactCb cb);

  void* GetRawBinder() override { return nullptr; }
};

}  // namespace fuzzing
}  // namespace grpc_binder

#endif  // GRPC_TEST_CORE_TRANSPORT_BINDER_END2END_FUZZERS_FUZZER_UTILS_H
